package controllers.data.providers;

import java.util.List;

import models.EntityContainer;
import models.PropertyModel;
import models.Target;

import org.openjena.atlas.web.HttpException;

import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.sparql.engine.http.QueryExceptionHTTP;


public class Sparql
{
    public static final String[] ILLEGAL_CHARACTERS = { "'", "\"" };
    public static final int      BASE_WAIT_TIME     = 64;
    public static final int      NUMBER_OF_TRIES    = 7;


    public static EntityContainer loadInstance(String instance, Target target)
    {
        // instance = removeIllegalCharacters(instance);

        Model model = ModelFactory.createDefaultModel();
        EntityContainer result = new EntityContainer(model.createResource(instance), target);

        List<QuerySolution> properties = Sparql.query("SELECT DISTINCT ?p {<" + instance + "> ?p ?v}", target);
        if(properties == null) { return result; }

        // load properties of the instance
        for(QuerySolution property : properties) {
            PropertyModel pm = new PropertyModel(property.getResource("?p"));
            result.addProperty(pm);

            // load values of the property
            List<QuerySolution> values = Sparql.query("SELECT ?v { <" + instance + "> <" + pm.getURI() + "> ?v }", target);
            for(QuerySolution value : values) {
                String propertyValue = value.get("?v").toString();

                if(propertyValue.contains("@") && propertyValue.substring(propertyValue.lastIndexOf("@")).length() == 3) {         // values with a language tag
                    String language = propertyValue.substring(propertyValue.lastIndexOf("@") + 1);
                    String content = propertyValue.substring(0, propertyValue.lastIndexOf("@"));

                    pm.addValue(language, content);
                } else {                                                                                                           // values without a language tag
                    pm.addValue("XX", propertyValue);
                    if(propertyValue.startsWith("http://dbpedia.org")) {
                        pm.linkedInstances.add(propertyValue);
                    }
                }
            }
        }

        // set readable name
        result.setReadableNames(result.getLocalName());

        return result;
    }


    public static String removeIllegalCharacters(String instance)
    {
        for(String i : ILLEGAL_CHARACTERS)
            instance = instance.replace(i, "");
        return instance;
    }


    /**
     * Send a sparql request to a destination - e.g. DBPEDIA
     * 
     * @param request
     *            sparql query
     * @param destination
     *            DBPEDIA or factforge
     * @return List of QuerySolutions
     */
    public static List<QuerySolution> query(String request, Target destination, Integer... beb)
    {
        // Initialization if not given
        if(beb == null || beb.length == 0) {
            beb = new Integer[1];
            beb[0] = 1;
        }
        // Fail silently after n retries
        else if(beb[0] > NUMBER_OF_TRIES)
            return null;
        QueryExecution qexec = Sparql.prepare(request, destination.url());

        List<QuerySolution> result;
        try {
            ResultSet results = qexec.execSelect();
            result = ResultSetFormatter.toList(results);
            qexec.close();
            // This is evil!
        } catch(HttpException | QueryExceptionHTTP e) {
            // Yago should fail silently!
            if(destination == Target.YAGO) { return null; }
            int waittime = (int) (Math.random() * ((BASE_WAIT_TIME << beb[0]) + 1));
            System.err.println("Querying too fast, retry (" + beb[0] + ") in " + waittime);
            System.err.println("Query: " + request + "(" + destination + ")");
            // e.printStackTrace();
            try {
                Thread.sleep(waittime);
            } catch(InterruptedException e1) {
                // Ignore
            }
            result = query(request, destination, ++beb[0]);
        }
        return result;
    }


    public static Boolean ask(String request, String destination)
    {
        QueryExecution qexec = Sparql.prepare(request, destination);

        boolean result = qexec.execAsk();
        qexec.close();

        return result;
    }


    private static QueryExecution prepare(String request, String destination)
    {
        String query = Namespace.toSparqlFormat() + request;

        Query sparqlQuery = QueryFactory.create(query);
        QueryExecution qexec = null;
        qexec = QueryExecutionFactory.sparqlService(destination, sparqlQuery);

        return qexec;
    }
}
